import os,sys

class jtas(object):
	instruction={
		# DATA MOVE
		'MOVAR' :0x40,
		'MOVRA' :0x80,
		'MOVBT' :0x20,
		'MOVTB' :0x30,
		'MOVHI' :0xF0,
		'MOVLO' :0xE0,
		# OPERATE A
		'NOT'   :0x18,
		'REOR'  :0x19,
		'REAND' :0x1A,
		'REXOR' :0x1B,
		'RL'    :0x1C,
		'RR'    :0x1D,
		'INC'   :0x1E,
		'DEC'   :0x1F,
		# OPERATE HIGH AND LOW
		'ADD'   :0x10,
		'SUB'   :0x11,
		'MUL'   :0x12,
		'DIV'   :0x13,
		'AND'   :0x14,
		'OR'    :0x15,
		'XOR'   :0x16,
		'COMP'  :0x17,
		# OPERATE MISC
		'NOP'   :0x00,
		'HEXTO' :0x02,
		'TOHEX' :0x03,
		'PUSH'  :0x04,
		'POP'   :0x05,
		'PIN'   :0x06,
		'POUT'  :0x07,
		'HALT'  :0x08,
		'CALL'  :0x09,
		'RET'   :0x0A,
		'JMP'   :0x0B,
		'JB'    :0x0F,
		'JNB'   :0x0E,
		'SETB'  :0x0D,
		'CLRB'  :0x0C,
		}

	errorinfo={
		10:'undefined instruction',
		11:'math operate only support plus and minus now',
		12:'illegal number or math operate',
		13:'illegal mov target, must be A or B',
		14:'the range of byte address is 00H~3FH(0~63)',
		15:'the range of bit address is 0H~FH(0~15)',
		16:'the range of unsigned 8 bit number is 00H~FFH(0~255),signed 8 bit is -128~127',
		17:'wrong EQU style, right style looks like this: LABE EQU 55',
		18:'wrong ORG style, right style looks like this: ORG 3AH',
		19:'can not use this label name, becasue this name has been used',
		}
		
	sysequdict={
		'HIGH'	:0x3F,
		'LOW'	:0x3E,
		'HEX_HI':0x3D,
		'HEX_LO':0x3C,
		'CY'	:0x7,
		'OV'	:0x6,
		'RS'	:0x5,
		'IE'	:0x4,
		'A.7'	:0xF,
		'A.6'	:0xE,
		'A.5'	:0xD,
		'A.4'	:0xC,
		'A.3'	:0xB,
		'A.2'	:0xA,
		'A.1'	:0x9,
		'A.0'	:0x8,
		}
		
	equdict={
		'HIGH'	:0x3F,
		'LOW'	:0x3E,
		'HEX_HI':0x3D,
		'HEX_LO':0x3C,
		'CY'	:0x7,
		'OV'	:0x6,
		'RS'	:0x5,
		'IE'	:0x4,
		'A.7'	:0xF,
		'A.6'	:0xE,
		'A.5'	:0xD,
		'A.4'	:0xC,
		'A.3'	:0xB,
		'A.2'	:0xA,
		'A.1'	:0x9,
		'A.0'	:0x8,
		}
		
	labledict={}

	def __init__(self, asmfile, hexfile='', outputverilog=False):
		self.hexi = [str(x) for x in range(10)] + [ chr(x) for x in range(ord('A'),ord('A')+6)]
		self.asmfile = asmfile
		if hexfile == '':
			self.hexfile = asmfile[:-3]+'.hex'
		else:
			self.hexfile = hexfile+'.hex'
		self.outputverilog = outputverilog
		#self.debugfile = asmfile+'.txt'
		# origin asm lines
		self.realasm = open(self.asmfile).readlines()
		# for wirte verilog rom init code
		self.romverilog=[]
		self.initdata()

	def initdata(self):
		self.realln = 0
		# asm lines have been proccessed
		self.asm = []
		self.ln = -1
		#  machine  code will write to file
		self.hexout = []
		# syntax error
		self.asmend = False
		self.syntaxerror = False
		self.syntaxerrors = []
		# var about generate hex file
		self.address = 0x00
		
	def perror(self):
		if len(self.syntaxerrors):
			print "syntax error : "
			for error in self.syntaxerrors:
				print 'line:',error[0],', ',error[1]

	def getrealln(self,ln=-1):
		'''get the line number current'''
		if ln == -1:
			ln = self.ln
		try:
			retval = self.asm[ln][1]
		except IndexError:
			retval = ''
		return retval
	
	def getasmaddr(self, ln=-1):
		if ln == -1:
			ln = self.ln
		try:
			retval = self.asm[ln][2]
		except IndexError:
			retval = ''
		return retval

	def getasm(self,ln=-1):
		'''get current asm'''
		if ln == -1:
			ln = self.ln
		try:
			retstr = self.asm[ln][0]
		except IndexError:
			retstr = ''
		return retstr
		
	#def nextasm(self):
	#	'''get current asm'''
	#	self.ln += 1
	#	try:
	#		retstr = self.asm[self.ln][0]
	#	except IndexError:
	#		retstr = ''
	#	return retstr

	def prepare(self, findlable, line):
		'''preprocess asm file, check syntax'''
		if self.asmend:
			return
		self.realln += 1
		commentstart = line.find(';')
		if commentstart != -1:
			line = line[:commentstart]
		line = line.strip()
		if len(line) == 0:
			return # that line is a comment line, without instruction
		line = line.upper()
		# check if this is a pseudoinstruction
		#pseudos = line.split(' ')
		#pword = []
		#for pseudo in pseudos:
		#	if pseudo != '':
		#		pword.append(pseudo)
		pword = line.split()
		if len(pword) > 2:
			if pword[1] == 'EQU':
				try:
					self.equdict[pword[0]] = self.calculator(pword[2])
				except IndexError:
					self.adderror(17)
				return
		elif pword[0] == 'ORG':
			if len(pword) != 2:
				self.adderror(18)
			else:
				orgaddr = self.calculator(pword[1])
				self.address = orgaddr
			return
		elif pword[0] == 'END':
			if len(pword) != 1:
				self.adderror(10)
			else:
				#self.realasm = self.realasm[:self.realln]
				self.asmend = True
			return
		#  find lable,  fist , i want to scan the lable just as other instruction,
		#  then , i found i can not konw the lable defined after it use
		#  so ,  i have to scan the file twice, find all label  first
		elif pword[0][-1] == ':':
			# it is a lable
			lablename = pword[0][:-1]
			if findlable:
				if self.equdict.has_key(lablename) or self.instruction.has_key(lablename):
					self.adderror(19)
				else:
					self.equdict[lablename] = self.address
					return
			else:
				if self.sysequdict.has_key(lablename) or \
				self.instruction.has_key(lablename) or	\
				self.labledict.has_key(lablename):
					self.adderror(19)
				else:
					self.labledict[lablename] = self.address
					return
				
		# now not a pseudoinstruction
		# check the instruction
		firstblankpos = line.find(' ')
		if firstblankpos == -1:
			#  now , current instruction is only one word !
			firstword = line
			if not self.instruction.has_key(firstword):
				#print '------------', firstword, 'not defined'
				self.adderror(10)   
			self.addasm(line)
			return

		else: # firstblankpos != -1:
			firstword = line[:firstblankpos]
			# now instruction has more than one word
			elseword = line[firstblankpos+1:]
			elseword = elseword.strip()
			if firstword == 'MOV' or firstword == 'LOAD':
				commapos = elseword.find(',')
				# store the addressor immediate number in the instruction
				number = 0 # i will let it to be decimal 
				if commapos != -1:
					try:
						left, right = elseword.split(',')
						left = left.strip()
						right = right.strip()
						numtype = ''
						if left == 'A' or left == 'B':
							number = self.calculator(right)
							if left == 'A':
								numtype = 'A'
								line = 'MOVAR %d' % number
							else:
								numtype = 'B'
								line = 'MOVBT %d' % number
						elif right == 'A' or right == 'B':
							number = self.calculator(left)
							if right == 'A':
								numtype = 'A'
								line = 'MOVRA %d' % number
							else:
								numtype = 'B'
								line = 'MOVTB %d' % number
						else:
							self.adderror(13)
						if numtype == 'A':
							if number > 63 or number < 0: # 63(0x3f)is the highest A address 
								self.adderror(14)
						elif numtype == 'B':
							if number > 15 or number < 0: # 15(0xf)is the highest B address
								self.adderror(15)
						self.addasm(line)
					except ValueError:
						self.adderror(10)
						self.adderror(13)
				else:
					# must be  load #8bit
					if elseword[0] == '#':
						elseword = elseword[1:]
					number = self.calculator(elseword)
					if number > 255 or number < -128: # 255(0xff) is the bigest 8 bit number
						self.adderror(16)
					if number < 0:
						number = 256 + number	# adjustment it as unsigned number
					#line = 'LOAD %d' % number
					line = 'MOVHI %d' % ((number & 0xF0) >> 4)
					self.addasm(line)
					line = 'MOVLO %d' % (number & 0x0F)
					self.addasm(line)
			else:
				# error
				self.adderror(10)
		return
		#self.ln += 1  #  innerl  asm  count from 0 ~ N ,  real line count from 1 ~ N

	def calculator(self, s):
		value = 0
		#   1+2-3-4+5    will  calcu like this:
		#  split to ['1', '2-3-4', '5']
		#   try , value +1 
		#   then  except,  split to ['2', '3', '4']
		#  we need add first number 2 and sub else number 3 and 4
		#  then return to try,  value + 5
		s = s.strip().upper()
		for num in s.split('+'):
			try:
				tmp = self.strtodecimal(num, True)
				value += tmp
			except ValueError:
				notplus = num.split('-')
				value += self.strtodecimal(notplus[0], False)
				notplus = notplus[1:]
				for minusnum in notplus:
					value -= self.strtodecimal(minusnum, False)

		return value

	def strtodecimal(self, s, raiseexcept):
		s = s.strip()
		value = 0
		if self.equdict.has_key(s):
			value = self.equdict[s]
		else:
			try:
				if s[-1] == 'H':
					value = int(s[:-1], 16)
				elif s[-1] == 'D':
					value = int(s[:-1], 10)
				elif s[-1] == 'O':
					value = int(s[:-1], 8)
				elif s[-1] == 'B':
					value == int(s[:-1], 2)
				else:
					#   dec do not need add that byte
					value = int(s, 10)
			except ValueError:
				value = 0
				if raiseexcept:
					raise ValueError
				else:
					self.adderror(12)
		return value

	def adderror(self, num):
		self.syntaxerrors.append((self.realln,self.errorinfo[num])) 

	def addasm(self, line):
		self.asm.append((line,self.realln,self.address))
		self.ln += 1
		self.address += 1
	
	def	tohex(self,decnum,base=16):
		'''	convert number from decimal to base 1~10'''
		assert decnum >= 0
		lis = []
		while True:
			decnum,rem = divmod(decnum,base)
			lis.append(self.hexi[rem])
			if decnum == 0:
				break
		retstr = ''.join([str(x) for x in lis[::-1]])
		assert len(retstr) <= 2
		if len(retstr) == 1:
			retstr = '0'+retstr
		return retstr

	def genhex(self, code):
		'''genarate hex file(intel style) from machine code'''
		addr = self.getasmaddr()
		out = ':0100'
		out += self.tohex(addr)
		out += '00'
		out += self.tohex(code)
		checkcode = (-1-addr-code) & 0xFF
		out += self.tohex(checkcode)
		self.hexout.append(out)
		self.romverilog.append((self.tohex(addr),self.tohex(code),self.getrealln()))
		
	def outverilog(self):
		if self.outputverilog:
			f = open(self.hexfile[:-3]+'v','w')
			for line in self.romverilog:
				vline = 'memory [8\'h%s] <= 8\'h%s ; // %s' % (line[0], line[1], self.realasm[line[2]])
				f.write(vline)
			f.close()
	
	def output(self):
		f = open(self.hexfile,'w')
		for line in self.hexout:
			print >> f, line
		f.close()
	
	def start(self):
		#for line in self.realasm:
		#	self.findlable(line)
		for line in self.realasm:
			self.prepare(True,line)		# findlable == true
		self.initdata()
		for line in self.realasm:
			self.prepare(False,line)	# findlable == False
		if len(self.syntaxerrors) != 0:
			self.syntaxerror = True
		if self.syntaxerror:
			self.perror()
			return
		#  no syntax error,  do next step: Assembly !
		self.ln = -1
		while True:
			self.ln += 1
			line = self.getasm()
			if line != '':
				asm = line.split(' ')
				lenasm = len(asm)
				assert len(asm) <= 2
				if len(asm) == 1:
					# one word instruction
					code = self.instruction[asm[0]]
				else:
					code = self.instruction[asm[0]]
					code = code | int(asm[1])
				
				self.genhex(code)
			else:
				# all asm has been translate to machine code!
				# now add the last line ( all intel hex file need this line)
				self.hexout.append(':00000001FF')
				break
		self.output()
		self.outverilog()


def main(argv):
	argc = len(argv) 
	outputverilog = False
	outfile = ''
	if (argc>5 or argc<2):
		print 'Usage: %s inputfile  [-o outputfile] [-v]' % argv[0]
		return
	else:
		for i in range(2,argc):
			arg = argv[i]
			if arg[0] == '-':
				if arg[1:] == 'v':
					outputverilog = True
				elif arg[1:] == 'o':
					try:
						outfile = argv[i+1]
					except IndexError:
						print 'Usage: %s inputfile  [-o outputfile] [-v]' % argv[0]
						return
				else:
					print 'Usage: %s inputfile  [-o outputfile] [-v]' % argv[0]
					return
		filename = argv[1]
		if filename.upper()[-2:] != 'JT':
			filename += '.jt'
		if not os.path.exists(filename):
			print 'Error, the input asm file "%s" do not exists' % argv[1]
		else:
			v1 = jtas(filename, outfile, outputverilog)
			v1.start()
	

if __name__ == '__main__':
	import sys,os
	main(sys.argv)
